package com.lumlord.util;

import cn.hutool.core.bean.BeanUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.lumlord.common.cache.CacheCenter;
import com.lumlord.common.exception.BusinessException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.stereotype.Component;

import com.lumlord.common.log.Log;
import com.lumlord.common.log.LogFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * @author: gpf
 * @date: 2019/10/10 10:31
 * @description: redis工具类
 */
@Lazy
@Component
public class RedisUtil implements RedisSerializer {
	private final Log logger = new LogFactory(getClass());

	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	@Autowired
	private RedisTemplate redisTemplate;

	/**
	 * 查询key,支持模糊查询
	 *
	 * @param key 传过来时key的前后端已经加入了*，或者根据具体处理
	 */
	public Set<String> keys(String key) {
		return stringRedisTemplate.keys(key);
	}

	/**
	 * 字符串获取值
	 * 
	 * @param key
	 */
	public Object get(String key) {
		return redisTemplate.opsForValue().get(key);
	}

	public Long getTimeOut(String key) {
		return redisTemplate.getExpire(key, TimeUnit.SECONDS);
	}

	public void put(String key, Object val, long timeOut) {
		redisTemplate.opsForValue().set(key, val, timeOut, TimeUnit.SECONDS);
	}

	public Object get(String key, Class clazz) {
		Object cacheVal = stringRedisTemplate.opsForValue().get(key);
		if (cacheVal != null) {
			if (cacheVal instanceof String) {
				Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();

				Object jsonVal = gson.fromJson(cacheVal.toString(), clazz);
				return jsonVal;
			} else {
				return cacheVal;
			}

		} else {
			return null;
		}

	}

	/**
	 * 字符串存入值 默认过期时间为2小时
	 * 
	 * @param key
	 */
	public void set(String key, String value) {
		stringRedisTemplate.opsForValue().set(key, value, 7200, TimeUnit.SECONDS);
	}

	/**
	 * 字符串存入值
	 * 
	 * @param expire 过期时间（秒）
	 * @param key
	 */
	public void set(String key, String value, Integer expire) {
		stringRedisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
	}

	/**
	 * 删出key 这里跟下边deleteKey（）最底层实现都是一样的，应该可以通用
	 * 
	 * @param key
	 */
	public void delete(String key) {
		stringRedisTemplate.opsForValue().getOperations().delete(key);
	}

	/**
	 * 添加单个 默认过期时间为两小时
	 * 
	 * @param key    key
	 * @param filed  filed
	 * @param domain 对象
	 */
	public void hset(String key, String filed, Object domain) {
		stringRedisTemplate.opsForHash().put(key, filed, domain);
	}

	/**
	 * 添加单个
	 * 
	 * @param key    key
	 * @param filed  filed
	 * @param domain 对象
	 * @param expire 过期时间（秒计）
	 */
	public void hset(String key, String filed, Object domain, Integer expire) {
		stringRedisTemplate.opsForHash().put(key, filed, domain);
		stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
	}

	/**
	 * 添加HashMap
	 *
	 * @param key key
	 * @param hm  要存入的hash表
	 */
	public void hset(String key, HashMap<String, Object> hm) {
		stringRedisTemplate.opsForHash().putAll(key, hm);
	}

	/**
	 * 如果key存在就不覆盖
	 * 
	 * @param key
	 * @param filed
	 * @param domain
	 */
	public void hsetAbsent(String key, String filed, Object domain) {
		stringRedisTemplate.opsForHash().putIfAbsent(key, filed, domain);
	}

	/**
	 * 查询key和field所确定的值
	 *
	 * @param key   查询的key
	 * @param field 查询的field
	 * @return HV
	 */
	public Object hget(String key, String field) {
		return stringRedisTemplate.opsForHash().get(key, field);
	}

	/**
	 * 查询该key下所有值
	 *
	 * @param key 查询的key
	 * @return Map<HK, HV>
	 */
	public Object hget(String key) {
		return stringRedisTemplate.opsForHash().entries(key);
	}

	/**
	 * 删除key下所有值
	 *
	 * @param key 查询的key
	 */
	public void deleteKey(String key) {
		redisTemplate.delete(key);
	}

	/**
	 * 删除key下所有值
	 *
	 * @param key 查询的key
	 */
	public void deleteCache(String key, Class clazz) {
		redisTemplate.delete(key);
	}

	/**
	 * 判断key和field下是否有值
	 *
	 * @param key   判断的key
	 * @param field 判断的field
	 */
	public Boolean hasKey(String key, String field) {
		return stringRedisTemplate.opsForHash().hasKey(key, field);
	}

	/**
	 * 判断key下是否有值
	 *
	 * @param key 判断的key
	 */
	public Boolean hasKey(String key) {
		return stringRedisTemplate.hasKey(key) || stringRedisTemplate.opsForHash().getOperations().hasKey(key);
	}

	/**
	 * 判断此token是否在黑名单中
	 *
	 * @param token
	 * @return
	 */
	public Boolean isBlackList(String token) {
		return hasKey("blacklist", token);
	}

	/**
	 * 将token加入到redis黑名单中
	 *
	 * @param token
	 */
	public void addBlackList(String token) {
		hset("blacklist", token, "true");
	}

	/**
	 * 查询token下的刷新时间
	 *
	 * @param token 查询的key
	 * @return HV
	 */
	public Object getTokenValidTimeByToken(String token) {
		return stringRedisTemplate.opsForHash().get(token, "tokenValidTime");
	}

	/**
	 * 查询token下的刷新时间
	 *
	 * @param token 查询的key
	 * @return HV
	 */
	public Object getUsernameByToken(String token) {
		return stringRedisTemplate.opsForHash().get(token, "username");
	}

	/**
	 * 查询token下的刷新时间
	 *
	 * @param token 查询的key
	 * @return HV
	 */
	public Object getIPByToken(String token) {
		return stringRedisTemplate.opsForHash().get(token, "ip");
	}

	/**
	 * 查询token下的过期时间
	 *
	 * @param token 查询的key
	 * @return HV
	 */
	public Object getExpirationTimeByToken(String token) {
		return stringRedisTemplate.opsForHash().get(token, "expirationTime");
	}

	public boolean isTokenExpired(String jwtToken) {
		String expirationTime = getExpirationTimeByToken(jwtToken).toString();
		return DateUtils.compareNow(expirationTime);
	}

	/**
	 * 查询token下的刷新时间
	 *
	 * @param token 查询的key
	 * @return HV
	 */
	public Object getIdByToken(String token) {
		return stringRedisTemplate.opsForHash().get(token, "id");
	}

	// class SerializeUtils implements RedisSerializer {
	public boolean isEmpty(byte[] data) {
		return (data == null || data.length == 0);
	}

	/**
	 * 序列化
	 * 
	 * @param object
	 * @return
	 * @throws SerializationException
	 */
	@Override
	public byte[] serialize(Object object) throws SerializationException {
		byte[] result = null;

		if (object == null) {
			return new byte[0];
		}
		try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128);
				ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream)) {

			if (!(object instanceof Serializable)) {
				throw new IllegalArgumentException(" requires a Serializable payload "
						+ "but received an object of type [" + object.getClass().getName() + "]");
			}

			objectOutputStream.writeObject(object);
			objectOutputStream.flush();
			result = byteStream.toByteArray();
		} catch (Exception ex) {
			logger.info("Failed to serialize", ex);
		}
		return result;
	}

	/**
	 * 序列化 list 集合
	 *
	 * @param list
	 * @return
	 */
	public static byte[] serializeList(List<?> list) {
		if (list == null || list.size() == 0) {
			return null;
		}
		ObjectOutputStream oos = null;
		ByteArrayOutputStream baos = null;
		byte[] bytes = null;
		try {
			baos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(baos);
			for (Object obj : list) {
				oos.writeObject(obj);
			}
			bytes = baos.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return bytes;
	}

	/**
	 * 反序列化 list 集合
	 *
	 * @param bytes
	 * @return
	 */
	public static List<?> unserializeList(byte[] bytes) {
		if (bytes == null) {
			return null;
		}
		List<Object> list = new ArrayList<Object>();
		ByteArrayInputStream bais = null;
		ObjectInputStream ois = null;
		try {
			// 反序列化
			bais = new ByteArrayInputStream(bytes);
			ois = new ObjectInputStream(bais);
			while (bais.available() > 0) {
				Object obj = (Object) ois.readObject();
				if (obj == null) {
					break;
				}
				list.add(obj);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}

	/**
	 * 反序列化
	 * 
	 * @param bytes
	 * @return
	 * @throws SerializationException
	 */
	@Override
	public Object deserialize(byte[] bytes) throws SerializationException {

		Object result = null;

		if (isEmpty(bytes)) {
			return null;
		}

		try (ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
				ObjectInputStream objectInputStream = new ObjectInputStream(byteStream)) {
			result = objectInputStream.readObject();
		} catch (Exception e) {
			logger.info("Failed to deserialize", e);
		}
		return result;
	}
	
	
	/**************************** redis list 开始***************************/ 
	 
    /**
     * 在变量左边添加元素值
     *
     * @param key
     * @param value
     * @return
     */
    public void leftPush(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }
 
    /**
     * 获取集合指定位置的值。
     *
     * @param key
     * @param index
     * @return
     */
    public Object index(String key, long index) {
        return redisTemplate.opsForList().index("list", index);
    }
 
    /**
     * 获取指定区间的值。
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public List<Object> range(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }
    
    
    
    /**
     * 获取指定区间的值。
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public List<Object> rangeAll(String key) {
        return redisTemplate.opsForList().range(key, 0, listLength(key));
    }
 
    /**
     * 把最后一个参数值放到指定集合的第一个出现中间参数的前面，
     * 如果中间参数值存在的话。
     *
     * @param key
     * @param pivot
     * @param value
     * @return
     */
    public void leftPush(String key, String pivot, String value) {
        redisTemplate.opsForList().leftPush(key, pivot, value);
    }
 
    /**
     * 向左边批量添加参数元素。
     *
     * @param key
     * @param values
     * @return
     */
    public void leftPushAll(String key, String... values) {
//        redisTemplate.opsForList().leftPushAll(key,"w","x","y");
        redisTemplate.opsForList().leftPushAll(key, values);
    }
 
    /**
     * 向集合最右边添加元素。
     *
     * @param key
     * @param value
     * @return
     */
    public void leftPushAll(String key, String value) {
        redisTemplate.opsForList().rightPush(key, value);
    }
 
    /**
     * 向左边批量添加参数元素。
     *
     * @param key
     * @param values
     * @return
     */
    public void rightPushAll(String key, String... values) {
      //redisTemplate.opsForList().leftPushAll(key,"w","x","y");
        redisTemplate.opsForList().rightPushAll(key, values);
    }
 
    /**
     * 向已存在的集合中添加元素。
     *
     * @param key
     * @param value
     * @return
     */
    public void rightPushIfPresent(String key, Object value) {
        redisTemplate.opsForList().rightPushIfPresent(key, value);
    }
 
    /**
     * 向已存在的集合中添加元素。
     *
     * @param key
     * @return
     */
    public long listLength(String key) {
        return redisTemplate.opsForList().size(key);
    }
 
    /**
     * 移除集合中的左边第一个元素。
     *
     * @param key
     * @return
     */
    public void leftPop(String key) {
        redisTemplate.opsForList().leftPop(key);
    }
 
    /**
     * 移除集合中左边的元素在等待的时间里，如果超过等待的时间仍没有元素则退出。
     *
     * @param key
     * @return
     */
    public void leftPop(String key, long timeout, TimeUnit unit) {
        redisTemplate.opsForList().leftPop(key, timeout, unit);
    }
 
    /**
     * 移除集合中右边的元素。
     *
     * @param key
     * @return
     */
    public void rightPop(String key) {
        redisTemplate.opsForList().rightPop(key);
    }
 
    /**
     * 移除集合中右边的元素在等待的时间里，如果超过等待的时间仍没有元素则退出。
     *
     * @param key
     * @return
     */
    public void rightPop(String key, long timeout, TimeUnit unit) {
        redisTemplate.opsForList().rightPop(key, timeout, unit);
    }
	
    
    /**************************** redis list 结束***************************/ 
    
	/**************************** redis 切库开始***************************/ 
	
	public void setDatabase(int index) {
		LettuceConnectionFactory jedisConnectionFactory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();
		 jedisConnectionFactory.setDatabase(index);
		 redisTemplate.setConnectionFactory(jedisConnectionFactory);
		 jedisConnectionFactory.resetConnection();
	}
	
	public void setDefDatabase() {
		setDatabase(defDatabase);
	}
	
	/**************************** redis 切库结束***************************/ 
	
	/**************************** redis 闹钟 start***************************/ 
	
	/**
	 * 闹钟缓存
	 * @param key
	 * @param clazz
	 * @param val
	 */
	public void registerAlarmClock(String key, Class clazz,Map<String,Object> val,int outOfSecond) {
		int expire = outOfSecond +week*60*60; //秒
		
		String cacheKey = CacheCenter.$(key,clazz);
		val.put("addTime", DateUtils.getAddDayHours(0));
		setDatabase(0); //数据缓存写入 0号库   保留 expire
		put(cacheKey, val, expire);
		
		String clockKey = CacheCenter.$Clock(key, clazz);
		setDefDatabase(); //恢复默认库
		put(clockKey, val, expire);
	}
	
	/**
	 * 获取闹钟 信息
	 * @param key
	 * @param clazz
	 * @return
	 */
	public Map getAlarmClock(String key, Class clazz) {
		setDatabase(0); //0号 库获取
		String cacheKey = CacheCenter.$(key,clazz);
		Object obj = get(cacheKey);
		setDefDatabase(); //恢复默认库
		if(null == obj) {
			return null;
		}
		return (Map) obj;
	}
	
	
	/**
	 * 获取闹钟 信息
	 * @param key
	 * @param clazz
	 * @return
	 */
	public Map getAlarmClock(String key,String familyClassString) {
		setDatabase(0); //0号 库获取
		String cacheKey = CacheCenter.$(familyClassString+":"+key);
		Object obj = get(cacheKey);
		setDefDatabase(); //恢复默认库
		if(null == obj) {
			return null;
		}
		return (Map) obj;
	}

	private int week = 24 * 7 ;// 一周
	
	@Value("${spring.redis.database}")
	private int defDatabase ;
	 
	/**************************** redis 闹钟 end ***************************/ 
}
